using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;


namespace WindowsGame1
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    /// 


    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        Texture2D ballTex;
        Texture2D lineTex;
        Texture2D line2Tex;
        Texture2D goalTex;
        Texture2D gravboxTex;
        Texture2D spikesTex;

        bool clicked;
        bool balldrop;
        bool playing;
        bool victory;
        Line currentline;
        public Vector2 gravity;
        const float rollThreshold = 150.0f;
        const float maxVelocity = 1200.0f;
        const float lineDeleteThreshold = 10.0f;

        Goal theGoal;
        Boolean goalAlive = false;



        ArrayList ballList = new ArrayList();
        ArrayList gravBoxList = new ArrayList();
        ArrayList linesList = new ArrayList();

        KeyboardState keyState = Keyboard.GetState();
        KeyboardState oldState = Keyboard.GetState();
        MouseState mouseState = Mouse.GetState();

        float futureGravityX = 0.0f;
        float futureGravityY = 0.0f;

        int drawState;

        bool pressed;
        Rectangle newRect;
        bool rightpressed;
        Circle newCircle;
        bool middlepressed;
        Line newHazard;
        int editvert;


        SpriteFont font;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            gravity = new Vector2(0.0f, 700.0f);


            this.IsMouseVisible = true;

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            goalTex = Content.Load<Texture2D>("goal");
            ballTex = Content.Load<Texture2D>("ball");
            lineTex = Content.Load<Texture2D>("line"); //TEMP
            line2Tex = Content.Load<Texture2D>("line2");
            gravboxTex = Content.Load<Texture2D>("gravbox");
            spikesTex = Content.Load<Texture2D>("spikes");

            font = Content.Load<SpriteFont>("SpriteFont1");

            //font = Content.Load<SpriteFont>("font");

            // TODO: use this.Content to load your game content here
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();


            keyState = Keyboard.GetState();
            mouseState = Mouse.GetState();


            if (!playing)
            {
                //Draws Ball
                if (keyState.IsKeyDown(Keys.B) && !oldState.IsKeyDown(Keys.B))
                {
                    addBall(new Vector2(mouseState.X, mouseState.Y));

                }


                //Set Gravity with mousewheel
                if (keyState.IsKeyDown(Keys.RightShift))
                {
                    futureGravityX = (float)mouseState.ScrollWheelValue;
                }

                if (keyState.IsKeyDown(Keys.RightControl))
                {
                    futureGravityY = (float)mouseState.ScrollWheelValue;
                }

                if (keyState.IsKeyDown(Keys.S) && !oldState.IsKeyDown(Keys.S))
                {
                    StreamWriter tempWriter = new StreamWriter("ourlevel.txt");
                    tempWriter.WriteLine(theGoal.getPosition().X);
                    tempWriter.WriteLine(theGoal.getPosition().Y);
                    tempWriter.WriteLine(theGoal.getRadius());
                    tempWriter.WriteLine(ballList.Count);
                    foreach (Ball aball in ballList)
                    {
                        tempWriter.WriteLine(aball.getPosition().X);
                        tempWriter.WriteLine(aball.getPosition().Y);
                    }
                    tempWriter.WriteLine(gravBoxList.Count);
                    foreach (GravityBox gbox in gravBoxList)
                    {
                        tempWriter.WriteLine(gbox.getPosition().X);
                        tempWriter.WriteLine(gbox.getPosition().Y);
                        tempWriter.WriteLine(gbox.getSize().X);
                        tempWriter.WriteLine(gbox.getSize().Y);
                        tempWriter.WriteLine(gbox.getGravity().X);
                        tempWriter.WriteLine(gbox.getGravity().Y);
                    }
                    tempWriter.WriteLine(linesList.Count);
                    foreach (Line aLine in linesList)
                    {
                        tempWriter.WriteLine(aLine.getVert1().X);
                        tempWriter.WriteLine(aLine.getVert1().Y);
                        tempWriter.WriteLine(aLine.getVert2().X);
                        tempWriter.WriteLine(aLine.getVert2().Y);
                        tempWriter.WriteLine(aLine.getRestitution());
                    }
                    tempWriter.Close();
                    tempWriter.Dispose();
                }

                if (keyState.IsKeyDown(Keys.L) && !oldState.IsKeyDown(Keys.L))
                {
                    ballList.Clear();
                    gravBoxList.Clear();
                    linesList.Clear();

                    StreamReader tempWriter = new StreamReader("ourlevel.txt");
                    theGoal = new Goal(0.0f);
                    theGoal.setPosition(
                        (float)Convert.ToDouble(tempWriter.ReadLine()),
                        (float)Convert.ToDouble(tempWriter.ReadLine()));
                    theGoal.setRadius(
                        (float)Convert.ToDouble(tempWriter.ReadLine()));
                    goalAlive = true;

                    int tempcount = Convert.ToInt32(tempWriter.ReadLine());
                    for (int c = 0; c < tempcount; c++)
                    {
                        float px = (float)Convert.ToDouble(tempWriter.ReadLine());
                        float py = (float)Convert.ToDouble(tempWriter.ReadLine());
                        Ball temp = new Ball(px, py, 11.5f);
                        temp.setStartPosition(new Vector2(px, py));
                        ballList.Add(temp);
                    }

                    tempcount = Convert.ToInt32(tempWriter.ReadLine());
                    for (int i = 0; i < tempcount; i++)
                    {
                        gravBoxList.Add(new GravityBox(
                            new Rectangle(
                                Convert.ToInt32(tempWriter.ReadLine()),
                                Convert.ToInt32(tempWriter.ReadLine()),
                                Convert.ToInt32(tempWriter.ReadLine()),
                                Convert.ToInt32(tempWriter.ReadLine())),
                            new Vector2(
                                (float)Convert.ToDouble(tempWriter.ReadLine()),
                                (float)Convert.ToDouble(tempWriter.ReadLine()))
                                ));
                    }

                    tempcount = Convert.ToInt32(tempWriter.ReadLine());
                    for (int i = 0; i < tempcount; i++)
                    {
                        linesList.Add(new Line(
                            (float)Convert.ToDouble(tempWriter.ReadLine()),
                            (float)Convert.ToDouble(tempWriter.ReadLine()),
                            (float)Convert.ToDouble(tempWriter.ReadLine()),
                            (float)Convert.ToDouble(tempWriter.ReadLine()),
                            (float)Convert.ToDouble(tempWriter.ReadLine()))
                            );
                    }

                    tempWriter.Close();
                    tempWriter.Dispose();
                }

                //Delete stuff
                if (keyState.IsKeyDown(Keys.Delete) && !oldState.IsKeyDown(Keys.Delete))
                {
                    deleteElementAt(mouseState.X, mouseState.Y);
                }


                //Draw gravity box with mouse

                if (mouseState.LeftButton == ButtonState.Pressed)
                {
                    if (!pressed)
                    {
                        //Mouse click
                        pressed = true;
                        newRect = new Rectangle(mouseState.X, mouseState.Y, 0, 0);
                    }
                    else
                    {
                        //Mouse hold
                        newRect.Width = mouseState.X - newRect.X;
                        newRect.Height = mouseState.Y - newRect.Y;
                    }
                }
                else
                {
                    if (pressed)
                    {
                        //Mouse release
                        pressed = false;
                        addGravBox(newRect, new Vector2(futureGravityX, futureGravityY));
                        newRect = new Rectangle(0, 0, 0, 0);
                    }
                }


                //Draw hazards with mouse
                if (mouseState.RightButton == ButtonState.Pressed)
                {
                    if (!rightpressed)
                    {
                        //Mouse click
                        rightpressed = true;
                        newHazard = new Line(mouseState.X, mouseState.Y, 0.0f, 0.0f, -1.0f);
                        linesList.Add(newHazard);
                        editvert = 1;
                    }
                    else
                    {
                        //Mouse hold
                        if (editvert == 1)
                        {
                            newHazard.setPosition(newHazard.getVert1().X, newHazard.getVert1().Y, mouseState.X, mouseState.Y);
                            if (newHazard.getVert1().X > newHazard.getVert2().X)
                            {
                                newHazard.setPosition(newHazard.getVert2(), newHazard.getVert1());
                                editvert = 2;
                            }
                        }
                        else
                        {
                            newHazard.setPosition(mouseState.X, mouseState.Y, newHazard.getVert2().X, newHazard.getVert2().Y);
                            if (newHazard.getVert1().X > newHazard.getVert2().X)
                            {
                                newHazard.setPosition(newHazard.getVert2(), newHazard.getVert1());
                                editvert = 1;
                            }
                        }
                    }
                }
                else
                {
                    if (rightpressed)
                    {
                        //Mouse release
                        rightpressed = false;
                        newHazard = null;
                    }
                }


                //Draw goal with mouse
                if (mouseState.MiddleButton == ButtonState.Pressed)
                {
                    if (!middlepressed)
                    {
                        //Mouse click
                        middlepressed = true;
                        goalAlive = true;
                        theGoal = new Goal(mouseState.X, mouseState.Y, 0.0f);
                    }
                    else
                    {
                        //Mouse hold
                        theGoal.setRadius(Vector2.Distance(new Vector2(mouseState.X, mouseState.Y), theGoal.getPosition()));
                    }
                }
                else
                {
                    if (middlepressed)
                    {
                        //Mouse release
                        middlepressed = false;
                    }
                }
            }


            //Toggle playing mode
            if (keyState.IsKeyDown(Keys.P) && !oldState.IsKeyDown(Keys.P) && theGoal != null)
            {
                playing = !playing;
            }


            oldState = keyState;


            ////////////////////////////////////////////////
            ///// RUN GAME

            if (playing)
            {

                if (keyState.IsKeyDown(Keys.Space) && !pressed)
                {
                    balldrop = !balldrop;
                    pressed = true;
                    if (!balldrop)
                    {
                        victory = false;
                        foreach (Ball b in ballList)
                        {
                            b.setPosition(b.getStartPosition());
                            b.setVelocity(Vector2.Zero);
                        }
                    }
                }

                if (!keyState.IsKeyDown(Keys.Space) && pressed)
                {
                    pressed = false;
                    foreach (Ball b in ballList)
                    {
                        b.freeze(false);
                    }
                }


                //MOUSE INPUT

                MouseState tempstate = Mouse.GetState();

                //User inputs lines
                if (tempstate.LeftButton == ButtonState.Pressed && !clicked)
                {
                    clicked = true;
                    editvert = 1;
                    currentline = new Line(tempstate.X, tempstate.Y, tempstate.X, tempstate.Y, 0.7f);
                    linesList.Add(currentline);
                }
                if (tempstate.LeftButton == ButtonState.Pressed && clicked)
                {
                    if (editvert == 1)
                    {
                        currentline.setPosition(currentline.getVert1().X, currentline.getVert1().Y, tempstate.X, tempstate.Y);
                        if (currentline.getVert1().X > currentline.getVert2().X)
                        {
                            currentline.setPosition(currentline.getVert2(), currentline.getVert1());
                            editvert = 2;
                        }
                    }
                    else
                    {
                        currentline.setPosition(tempstate.X, tempstate.Y, currentline.getVert2().X, currentline.getVert2().Y);
                        if (currentline.getVert1().X > currentline.getVert2().X)
                        {
                            currentline.setPosition(currentline.getVert2(), currentline.getVert1());
                            editvert = 1;
                        }
                    }
                }

                //User deletes lines
                if (tempstate.RightButton == ButtonState.Pressed && !clicked)
                {
                    clicked = true;
                    Line removeMe = null;
                    foreach (Line tline in linesList)
                    {
                        if (tline.getDistanceTo(tempstate.X, tempstate.Y) < lineDeleteThreshold)
                        {
                            removeMe = tline;
                        }
                    }
                    linesList.Remove(removeMe);
                }

                if (tempstate.LeftButton == ButtonState.Released &&
                    tempstate.RightButton == ButtonState.Released &&
                    clicked)
                {
                    clicked = false;
                    currentline = null;
                }


                if (balldrop)
                {
                    foreach (Ball b in ballList)
                    {
                        //Max velocity
                        if (b.getVelocity().Length() > maxVelocity)
                        {
                            b.setVelocity(b.getVelocity() * (maxVelocity / b.getVelocity().Length()));
                        }


                        //Determine which gravity to use
                        Vector2 usegravity = gravity;
                        foreach (GravityBox gbox in gravBoxList)
                        {
                            if (gbox.getGravbox().Contains((int)b.getPosition().X, (int)b.getPosition().Y))
                            {
                                usegravity = gbox.getGravity();
                            }
                        }

                        if (!b.frozen())
                        {
                            b.move((float)gameTime.ElapsedGameTime.TotalSeconds);
                            b.accelerate(usegravity, (float)gameTime.ElapsedGameTime.TotalSeconds);
                        }

                        //Check victory
                        if (theGoal.ballIn(b))
                        {
                            victory = true;
                            b.freeze(true);
                        }

                        //Check collision with lines
                        foreach (Line tline in linesList)
                        {
                            if (tline.getDistanceTo(b.getPosition().X, b.getPosition().Y) < b.getRadius())
                            {
                                if (tline.getRestitution() >= 0.0f)
                                {
                                    //Move back out of line
                                    b.moveBack((float)gameTime.ElapsedGameTime.TotalSeconds);

                                    //If component of vector into line is small enough, roll
                                    //otherwise bounce
                                    Vector2 parallel = new Vector2();
                                    Vector2 perpendicular = new Vector2();

                                    double sS = Vector2.Dot(b.getVelocity(), tline.getDirectionalVector());
                                    double vB = tline.getDirectionalVector().Length();

                                    double sLenParallel = sS / vB;

                                    parallel.X = (float)(sLenParallel * Math.Cos(tline.getAngle()));
                                    parallel.Y = (float)(sLenParallel * Math.Sin(tline.getAngle()));

                                    perpendicular = b.getVelocity() - parallel;

                                    if (Math.Abs(perpendicular.Length()) <= rollThreshold)
                                    {
                                        //Roll
                                        b.setVelocity(parallel);
                                    }
                                    else
                                    {
                                        //Bounce
                                        b.bounce(tline);
                                    }
                                }
                                else
                                {
                                    //HAZARD
                                    b.freeze(true);
                                }

                                
                            }
                        }

                    }

                }

            }

            base.Update(gameTime);
        }


        private void deleteElementAt(int posX, int posY)
        {
            Vector2 tocheck = new Vector2(posX,posY);

            Ball toRemoveBall = null;
            GravityBox toRemoveBox = null;
            Line toRemoveLine = null;
            
            foreach (Ball b in ballList)
            {
                Rectangle r = new Rectangle((int)b.getPosition().X - (int)ballTex.Width / 2, (int)b.getPosition().Y - (int)ballTex.Height / 2, (int)ballTex.Width, (int)ballTex.Height);

                if (r.Contains(new Point(posX,posY)))
                {
                    toRemoveBall = b;
                }
                
            }
            foreach (GravityBox g in gravBoxList)
            {
                if (g.getGravbox().Contains(new Point(posX, posY)))
                {
                    toRemoveBox = g;
                }
            }
            foreach (Line l in linesList)
            {
                if (l.getDistanceTo(posX, posY) < 8)
                {
                    toRemoveLine = l;
                }
            }

            if (toRemoveBox != null)
                gravBoxList.Remove(toRemoveBox);
            if (toRemoveBall != null)
                ballList.Remove(toRemoveBall);
            if (toRemoveLine != null)
                linesList.Remove(toRemoveLine);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend);


            //Draw victory message
            if (victory)
            {
                spriteBatch.DrawString(
                    font,
                    "You win!",
                    new Vector2(400.0f, 400.0f),
                    Color.White);
            }

            if (playing)
            {
                spriteBatch.DrawString(
                        font,
                        "Playing",
                        new Vector2(10.0f, 10.0f),
                        Color.White);
            }

          
                foreach (Ball theBall in ballList)
                {
                    spriteBatch.Draw(
                        ballTex,
                        theBall.getPosition(),
                        null,
                        Color.White,
                        0.0f,
                        new Vector2(theBall.getRadius(), theBall.getRadius()),
                        new Vector2(1.0f, 1.0f),
                        SpriteEffects.None,
                        0.8f);
                

                }

                foreach (GravityBox gbox in gravBoxList)
                {
                    spriteBatch.Draw(
                        gravboxTex,
                        gbox.getPosition(),
                        null,
                        Color.White,
                        0.0f,
                        Vector2.Zero,
                        gbox.getSize() / 100.0f,
                        SpriteEffects.None,
                        1.0f);
                }

                spriteBatch.DrawString(
                        font,
                        futureGravityX.ToString(),
                        new Vector2(400.0f, 400.0f),
                        Color.White);

                spriteBatch.DrawString(
                       font,
                       futureGravityY.ToString(),
                       new Vector2(400.0f, 450.0f),
                       Color.White);

                //Draw lines
                foreach (Line aLine in linesList)
                {
                    Vector2 temp;
                    if (aLine.getVert1().X < aLine.getVert2().X)
                        temp = aLine.getVert1();
                    else
                        temp = aLine.getVert2();

                    Texture2D usetex = lineTex;

                    if (aLine.getRestitution() < 0)
                        usetex = spikesTex;

                    spriteBatch.Draw(
                        usetex,
                        temp,
                        null,
                        Color.White,
                        aLine.getAngle(),
                        Vector2.Zero,
                        new Vector2(aLine.getLength() / 100.0f, 1.0f),
                        SpriteEffects.None,
                        0.7f);
                }

                if (goalAlive)
                {
                    float gS = theGoal.getRadius() / (goalTex.Width / 2);
                    spriteBatch.Draw(
                        goalTex,
                        theGoal.getPosition(),
                        null,
                        Color.White,
                        0.0f,
                        new Vector2(goalTex.Width / 2.0f, goalTex.Height / 2.0f),
                        new Vector2(gS, gS),
                        SpriteEffects.None,
                        0.3f);
                }
            spriteBatch.Draw(gravboxTex, newRect, Color.White);


            spriteBatch.End();
            base.Draw(gameTime);
        }

        //adds ball
        public void addBall(Vector2 position)
        {

            Ball newBall = new Ball(position, 11.5f);
            newBall.setStartPosition(position);
            ballList.Add(newBall);

        }




        public void addGravBox(Rectangle r, Vector2 newGrav)
        {
            gravBoxList.Add(new GravityBox(r, newGrav));

        }

    }

    
    public class Circle
    {
        public float X;
        public float Y;
        public float radius;

        public Circle(float Xin, float Yin, float radin)
        {
            X = Xin;
            Y = Yin;
            radius = radin;
        }
    }




    }